package com.google.code.b0rx0r.advancedSamplerEngine.effect.modulation;

import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;

public class EnvelopeModulation implements Modulation {

	private SortedSet<Point> points = new TreeSet<Point>();
	private EnvelopeInterpolationStrategy eis = new LinearEnvelopeInterpolationStrategy();
	
	public EnvelopeModulation addPoint(int offset, float value) {
		Point p = new Point(offset, value);
		points.add(p);
		
		return this;
	}
	
	@Override
	public float getValue(long offset) {
		//no points => behave neutral
		if (points.isEmpty())
			return 1f;
		

		//invariant: there is at least one point in the envelope.
		assert points.size() > 0;

		//if we're before the first or past the last point, we'll use their respective values
		//this also implies that if there is only one point in the envelope we'll use its value (since we're either before-or-on or past-or-on it)
		Point first = points.first();
		Point last = points.last();
		
		if (offset <= first.offset) {
			return first.value;
		}
	
	  if (offset >= last.offset) { 
 		  return last.value;
	  }
	

	  
	  //invariant: there are at least two points in the envelope.
	  //invariant: also, the given offset lies between the offsets of two points in the envelope
		assert points.size() > 1;
		
		//find the two neighbouring points and interpolate, or find the point we're exactly on and return its value 
		Iterator<Point> it = points.iterator();
		Point current = it.next();
		Point previous = null;
		
		while (true) {
		  if (current.offset == offset)
			  return current.value;
		  
		  //hooray!
		  if (current.offset > offset) {
		  	return eis.getValue(previous.value, (int)(offset - previous.offset), current.value, current.offset - previous.offset);
		  }
		  
		  //not yet there
		  previous = current;
		  current = it.next();
		}
	}
	
	
	private static class Point implements Comparable<Point> {
		public int offset;
		public float value;
		
		public Point(int offset, float value) {
			this.offset = offset;
			this.value = value;
		}

		@Override
		public int compareTo(Point o) {
			return offset - o.offset;
		}
		
		
	}


	@Override
	public void setGlobalOffset(long offset) {
		// the envelope is synced to its modulation target and no global offset is required
		
	}

}
